package com.zkh.myutils.io.excel;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import com.zkh.myutils.io.IOUtils;
import com.zkh.myutils.io.excel.annotation.ExcelColumn;
import com.zkh.myutils.io.excel.annotation.ExcelFile;
import com.zkh.myutils.io.excel.worksheet.CellStyle;
import com.zkh.myutils.io.excel.worksheet.Row;
import com.zkh.myutils.io.excel.worksheet.Sheet;
import com.zkh.myutils.io.excel.worksheet.style.Align;
import com.zkh.myutils.io.excel.worksheet.style.Alignment;
import com.zkh.myutils.io.excel.worksheet.style.Border;
import com.zkh.myutils.io.excel.worksheet.style.BorderStyle;
import com.zkh.myutils.io.excel.worksheet.style.Font;
import com.zkh.myutils.io.excel.worksheet.style.Style;
import com.zkh.myutils.utils.Assert;
import com.zkh.myutils.utils.AuthTree;
import com.zkh.myutils.utils.StringUtils;
import com.zkh.myutils.utils.Utils;
import com.zkh.myutils.utils.UtilsException;

/**
 * Excel辅助工具类
 * @author zkh
 */
public class ExcelUtils {
	
	/**
	 * 根据列字母获取当前列数
	 * @param columnLetter 列字母
	 */
	public static int getColumnNumber(String columnLetter) {
		//最终结果
		int res = 0;
		//分割字母
		char[] chars = columnLetter.toUpperCase().toCharArray();
		//遍历
		for(int i=0,len=chars.length; i<len; i++) {
			//当前编码
			int code = (int) chars[len-i-1] - 64;
			//累加
			res += code * Math.pow(26, i);
		}
		//返回结果
		return res;
	}
	
	/**
	 * 获取当前列对应的列字母
	 * @param columnNumber 当前列数
	 */
	public static String getColumnLetter(int columnNumber) {
		//每一位数字
		List<Integer> letterList = new ArrayList<>();
		//开始遍历
		while(columnNumber>26) {
			//取余数
			letterList.add(columnNumber%26);
			//取值
			columnNumber /= 26;
		}
		//末位数字
		letterList.add(columnNumber);
		//最终容器
		StringBuilder sb = new StringBuilder();
		//遍历，同时处理进位的总是
		for(int i=0,len=letterList.size(); i<len; i++) {
			//当前数字
			int c = letterList.get(i);
			//最后一位是0，则直接跳过
			if(i==len-1 && c==0) {
				continue;
			}
			//为0
			if(c==0) {
				//拼接Z
				c = 26;
				//前一位减1
				letterList.set(i+1, letterList.get(i+1)-1);
			}
			//拼接
			sb.append((char) (c + 64));
		}
		//返回
		return sb.reverse().toString();
	}
	
	/**
	 * 获取XLSX文件的ContentType
	 */
	public static String getContentType() {
		return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
	}
	
	/**
	 * 保存为Excel
	 * @param entityList 导出实体列表
	 * @param filePath 文件路径
	 * @param params 表格标题占位数据（标题可包含动态数据，使用占位符“{索引}”，通过该参数依次进行替换）
	 */
	public static <T> void saveAsExcel(List<T> entityList, String filePath, Object... params) throws IOException{
		saveAsExcel(entityList, new File(filePath), params);
	}
	
	/**
	 * 保存为Excel
	 * @param entityList 导出实体列表
	 * @param file Excel文件对象
	 * @param params 表格标题占位数据（标题可包含动态数据，使用占位符“{索引}”，通过该参数依次进行替换）
	 */
	public static <T> void saveAsExcel(List<T> entityList, File file, Object... params) throws IOException{
		Objects.requireNonNull(entityList);
		//获取第一个实体，用来获取字段信息
		T t = entityList.isEmpty() ? null : entityList.get(0);
		//创建对象
		WorkbookWriter workbookWriter = new WorkbookWriter();
		//创建sheet
		Sheet sheet;
		//没有数据
		if(t==null) {
			sheet = workbookWriter.createSheet("sheet1");
		}//有数据
		else {
			//标题
			String headText = "";
			/* 处理样式 */
			//样式对象，表头样式
			CellStyle headStyle = null;
			//获取实体class
			Class<?> clazz = t.getClass();
			//获取注解
			ExcelFile excelFile = clazz.getAnnotation(ExcelFile.class);
			//为空
			if(excelFile==null) {
				sheet = workbookWriter.createSheet("sheet1");
			}else {
				sheet = workbookWriter.createSheet(excelFile.sheetName());
				//有标题
				headText = StringUtils.replaceByIndex(excelFile.head(), params);
				//不为空
				if(!headText.isEmpty()) {
					//加载样式
					headStyle = getCellStyle(excelFile);
				}
			}
			//字段容器
			TreeMap<Integer, Method> fieldMap = new TreeMap<>();
			Map<Integer, CellStyle> styleMap = new HashMap<>();
			Map<Integer, Object[]> otherMap = new HashMap<>();
			//读取字段
			Class<?> columnsClass = t.getClass();
			//获取所有字段
			while(columnsClass!=null) {
				try {
					//读取字段
					for(Field f: columnsClass.getDeclaredFields()) {
						//获取注解
						ExcelColumn column = f.getAnnotation(ExcelColumn.class);
						//有
						if(column!=null) {
							fieldMap.put(column.index(), columnsClass.getMethod("get" + StringUtils.uppercaseFirst(f.getName())));
							//样式
							CellStyle cellStyle = getCellStyle(column);
							//保存样式
							if(!cellStyle.isEmpty()) {
								styleMap.put(column.index(), cellStyle);
							}
							otherMap.put(column.index(), new Object[] {column.name(), column.width(), column.height()});
						}
					}
				}catch (NoSuchMethodException e) {
					throw new RuntimeException("请提供Getter", e);
				}
				//查父类
				columnsClass = columnsClass.getSuperclass();
			}
			/* 生成Excel文件的临时变量 */
			//总列数
			int len = fieldMap.size();
			//行变量
			Row row;
			/* 正式生成文件 */
			//标题
			if(!headText.isEmpty()) {
				//创建新行
				row = sheet.createRow();
				row.setHeight(excelFile.headHeight());
				//遍历列
				for(int i=0;i<len;i++) {
					//写入标题
					row.createCell(headText).setCellStyle(headStyle);
				}
				//合并
				sheet.addMerge(1, 1, 1, len);
			}
			//读取到字段
			if(!fieldMap.isEmpty()) {
				try {
					//Entry
					Set<Entry<Integer, Method>> entry = fieldMap.entrySet();
					//创建表头行
					row = sheet.createRow();
					//克隆样式
					CellStyle titleStyle = styleMap.getOrDefault(fieldMap.firstKey(), new CellStyle()).clone();
					//默认加粗
					Font font = titleStyle.getFont();
					//为空
					if(font==null) {
						titleStyle.setFont(font = new Font());
					}
					font.setBold(true);
					//居中
					titleStyle.setAlignment(new Alignment(Align.HORIZONTAL_CENTER, Align.VERTICAL_CENTER));
					//列宽
					Integer[] widths = new Integer[len]; int i=0;
					//写入表头
					for(Entry<Integer, Method> kv: entry) {
						//获取内容
						Object[] vs = otherMap.get(kv.getKey());
						//写入表头
						row.createCell(vs[0]).setCellStyle(titleStyle);
						row.setHeight((int) vs[2]);
						//列宽
						widths[i++] = (int) vs[1];
					}
					sheet.setColumnsWidth(widths);
					//遍历数据
					for(T e: entityList) {
						//创建新行
						row = sheet.createRow();
						//处理列
						for(Entry<Integer, Method> kv: entry) {
							//写入数据
							row.createCell(kv.getValue().invoke(e)).setCellStyle(styleMap.get(kv.getKey()));
						}
					}
				}catch (IllegalAccessException | IllegalArgumentException e) {
					throw new RuntimeException(e);
				}catch (InvocationTargetException e) {
					throw new RuntimeException(e.getTargetException());
				}
			}
		}
		File temp;
		//目录
		if(!(temp = file.getParentFile()).isDirectory()) {
			temp.mkdirs();
		}
		//文件
		if(!file.isFile()) {
			file.createNewFile();
		}
		//目标文件
		try(FileOutputStream fos = new FileOutputStream(file)){
			//写入数据
			workbookWriter.write(fos);
		}
	}
	
	/**
	 * 获取样式
	 */
	private static CellStyle getCellStyle(ExcelFile excelFile) {
		//样式对象
		CellStyle style = new CellStyle();
		//默认居中
		style.setAlignment(new Alignment(Align.HORIZONTAL_CENTER, Align.VERTICAL_CENTER));
		//加载样式
		AuthTree authTree = new AuthTree(excelFile.headStyle());
		/* 字体（默认加粗） */
		Font font = new Font(excelFile.headFontFamily(), excelFile.headFontSize(), true);
		//颜色
		Assert.doIfNotEmpty(excelFile.headFontColor(), font::setColor);
		//读取样式
		renderStyle(style, authTree, font);
		/* 背景色 */
		Assert.doIfNotEmpty(excelFile.headBgColor(), style::setBackgroundColor);
		//返回样式对象
		return style;
	}

	/**
	 * 获取样式
	 */
	private static CellStyle getCellStyle(ExcelColumn excelColumn) {
		//样式对象
		CellStyle style = new CellStyle();
		//加载样式
		AuthTree authTree = new AuthTree(excelColumn.style());
		/* 字体 */
		Font font = new Font(excelColumn.fontFamily(), excelColumn.fontSize());
		//颜色
		Assert.doIfNotEmpty(excelColumn.fontColor(), font::setColor);
		//读取样式
		renderStyle(style, authTree, font);
		/* 背景色 */
		Assert.doIfNotEmpty(excelColumn.bgColor(), style::setBackgroundColor);
		//返回样式对象
		return style;
	}
	
	/**
	 * 读取样式
	 */
	private static void renderStyle(CellStyle style, AuthTree authTree, Font font) {
		//加粗、斜体、下划线
		Assert.doIfTrue(authTree.hasAuth(Style.FONT_BOLD), ()->font.setBold(true));
		Assert.doIfTrue(authTree.hasAuth(Style.FONT_ITALIC), ()->font.setItalic(true));
		Assert.doIfTrue(authTree.hasAuth(Style.FONT_UNDERLINE), ()->font.setUnderline(true));
		//保存字体样式
		style.setFont(font);
		/* 边框样式 */
		BorderStyle bs = new BorderStyle();
		Border border = new Border();
		Assert.doIfTrue(authTree.hasAuth(Style.BORDER_LEFT), ()->border.setBorderLeft(bs));
		Assert.doIfTrue(authTree.hasAuth(Style.BORDER_TOP), ()->border.setBorderTop(bs));
		Assert.doIfTrue(authTree.hasAuth(Style.BORDER_RIGHT), ()->border.setBorderRight(bs));
		Assert.doIfTrue(authTree.hasAuth(Style.BORDER_BOTTOM), ()->border.setBorderBottom(bs));
		Assert.doIfTrue(authTree.hasAuth(Style.BORDER_ALL), ()->border.setBorder(bs));
		//有边框
		Assert.doIfTrue(!border.isEmpty(), ()->style.setBorder(border));
		/* 对齐方式 */
		Alignment align = new Alignment();
		Assert.doIfTrue(authTree.hasAuth(Style.ALIGN_VERTICAL_TOP), ()->align.setVertical(Align.VERTICAL_TOP));
		Assert.doIfTrue(authTree.hasAuth(Style.ALIGN_VERTICAL_CENTER), ()->align.setVertical(Align.VERTICAL_CENTER));
		Assert.doIfTrue(authTree.hasAuth(Style.ALIGN_VERTICAL_BOTTOM), ()->align.setVertical(Align.VERTICAL_BOTTOM));
		Assert.doIfTrue(authTree.hasAuth(Style.ALIGN_HORIZONTAL_LEFT), ()->align.setHorizontal(Align.HORIZONTAL_LEFT));
		Assert.doIfTrue(authTree.hasAuth(Style.ALIGN_HORIZONTAL_CENTER), ()->align.setHorizontal(Align.HORIZONTAL_CENTER));
		Assert.doIfTrue(authTree.hasAuth(Style.ALIGN_HORIZONTAL_RIGHT), ()->align.setHorizontal(Align.HORIZONTAL_RIGHT));
		//有对齐
		Assert.doIfTrue(!align.isEmpty(), ()->style.setAlignment(align));
	}
	
	/**
	 * 生成标准格式的excel数据
	 * @param sheetname sheet名称
	 * @param headers 数据字段名
	 * @param contents 数据
	 */
	public static WorkbookWriter createWorkbookWriter(String sheetname, List<String> headers, List<Object[]> contents){
		return createWorkbookWriter(sheetname, null, headers, contents);
	}
	
	/**
	 * 生成标准格式的excel数据
	 * @param sheetname sheet名称
	 * @param head Excel文件内标题
	 * @param headers 数据字段名
	 * @param contents 数据
	 */
	public static WorkbookWriter createWorkbookWriter(String sheetname, String head, List<String> headers, List<Object[]> contents) {
		//创建对象
		WorkbookWriter workbookWriter = new WorkbookWriter();
		//创建sheet
		Sheet sheet = workbookWriter.createSheet(sheetname);
	    //总列数
	    int len = headers.size();
	    //行
	    Row row = sheet.createRow();
    	//表头字体
    	Font font = new Font("宋体", true);
		//首行标题
	    if(!StringUtils.isEmpty(head)) {
	    	//表头样式
	    	CellStyle headstyle = new CellStyle();
	    	//字体
	    	headstyle.setFont(font);
	    	//对齐方式
	    	headstyle.setAlignment(new Alignment(Align.HORIZONTAL_CENTER, Align.VERTICAL_CENTER));
	    	//遍历列
	    	for(int i=0;i<len;i++) {
	    		//写入标题
	    		row.createCell(head).setCellStyle(headstyle);
	    	}
	    	//合并
	    	sheet.addMerge(1, 1, 1, len);
	    	//创建新行
	    	row = sheet.createRow();
	    }
	    //表头样式
	    CellStyle titlestyle = new CellStyle();
	    //字体
	    titlestyle.setFont(font);
	    // 设置表头
	    for(int i=0;i<len;i++){
	    	row.createCell(headers.get(i)).setCellStyle(titlestyle);
	    }
	    //循环写入数据
	    for(Object[] cols: contents) {
	    	//创建行
	    	row = sheet.createRow();
	    	//遍历写数据
	    	for(int i=0;i<len;i++){
	    		row.createCell(cols[i]);
	    	}
	    }
		//返回
		return workbookWriter;
	}
	
	/**
	 * 将多个Excel对象压缩为一个ZIP包(JDK7以下压缩包内文件名会乱码)
	 * @param wbs Excel数据对象
	 * @param filenames 每个Excel的文件名，与wbs一一对应。
	 */
	public static InputStream createZip(List<WorkbookWriter> wbs, List<String> filenames){
		//检测参数
		if(wbs==null || filenames==null || wbs.size()!=filenames.size()){
			throw new UtilsException("参数不能为null，且集合大小必须相同");
		}
		//声明变量
		File file = null;
		ZipOutputStream zos = null;
		OutputStream os = null;
		try{
			//创建临时文件
			file = File.createTempFile(Utils.getUUID(), null);
			//输出流
			os = Files.newOutputStream(file.toPath());
			//zip流
			zos = new ZipOutputStream(os);
			//遍历
			for(int i=0,len=wbs.size();i<len;i++){
				//添加文件
				zos.putNextEntry(new ZipEntry(filenames.get(i)));
				//写入字节流
				wbs.get(i).write(zos);
			}
			//返回流
			return Files.newInputStream(file.toPath());
		}catch(IOException e){
			throw new RuntimeException(e);
		} finally{
			//关闭流
			IOUtils.closeQuietly(zos, os);
			//删除临时文件
			if(file!=null) {
				file.deleteOnExit();
			}
		}
	}
}
